package com.anlu.base.utils;


import com.anlu.base.annotaions.SignParam;
import com.anlu.base.exception.BaseException;
import com.anlu.base.exception.ForbiddenException;
import com.anlu.base.model.ResultBean;
import com.anlu.base.model.ao.BaseSignAO;
import com.anlu.base.model.errorcode.CommonErrCode;
import com.anlu.base.sign.ParamSecureProperties;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Array;
import java.util.*;


@Component
public class SignUtil {
    private static final Logger logger = LoggerFactory.getLogger(SignUtil.class);


    private static ParamSecureProperties paramSecureProperties;

    @Autowired
    public void setParamSecureProperties(ParamSecureProperties paramSecureProperties) {
        SignUtil.paramSecureProperties = paramSecureProperties;
    }

    private static String join(String a, String b) {
        return a + "," + b;
    }

    /**
     * 方法描述：获取需要sign的属性
     *
     * @param signBO
     * @return java.util.List<java.lang.String>
     * @author 赵希坤
     * @date 日期：2023/7/16 时间：21:39
     */
    public static List<String> signParamsList(Object signBO) {
        List<String> list = new ArrayList<>();
        BeanReflectUtil.getRWMethod(signBO).forEach((k, v) -> {
            SignParam annotation = v.getField().getAnnotation(SignParam.class);
            if (null != annotation) {
                list.add(k);
            }
        });
        return list;
    }

    /**
     * 方法描述：根据对象生成签名
     *
     * @param bo   待签名的对象
     * @param salt 盐值
     * @return ResultBean<String> 成功后的sign值
     * @author 赵希坤
     * @date 日期：2022/11/17 时间：14:34
     */
    public static ResultBean<String> sign(Object bo, String salt) {
        return sign(bo, salt, null);
    }

    /**
     * 方法描述：根据对象生成签名
     *
     * @param bo         待签名的对象
     * @param salt       盐值
     * @param signParams 需要签名的参数
     * @return ResultBean<String> 成功后的sign值
     * @author 赵希坤
     * @date 日期：2022/11/17 时间：14:34
     */
    public static ResultBean<String> sign(Object bo, String salt, Collection<String> signParams) {
        logger.debug("~~~~~~ 0 sign {} {} {}", bo, salt, signParams);
        if (null == bo || StringUtils.isEmpty(salt)) {
            ResultBean<String> stringResultBean = ResultBean.resultCode(CommonErrCode.INVALID_PARAM_VALUE);
            stringResultBean.e(new BaseException(CommonErrCode.INVALID_PARAM_VALUE).args("bo", "salt"));
            return stringResultBean;
        }
        Class<?> clazz = bo.getClass();
        if (Object.class.equals(clazz)) {
            ResultBean<String> stringResultBean = ResultBean.resultCode(CommonErrCode.INVALID_PARAM_VALUE);
            stringResultBean.e(new BaseException(CommonErrCode.INVALID_PARAM_VALUE).args("bo"));
            return stringResultBean;
        }
        //string 基本类型 枚举 object
        if (CharSequence.class.isAssignableFrom(clazz)
                || clazz.isEnum()
                || isPrimitive(clazz)) {
            String data = bo.toString() + salt;
            ResultBean<String> success = ResultBean.success(DigestUtils.md5Hex(data), data);
            logger.debug("~~~~~~ 1 sign {}{} {}", bo, salt, success.getBean());
            return success;
        }
        //数组
        if (clazz.isArray()) {
            int len = Array.getLength(bo);
            if (len == 0) {
                ResultBean<String> stringResultBean = ResultBean.resultCode(CommonErrCode.INVALID_PARAM_VALUE);
                stringResultBean.e(new BaseException(CommonErrCode.INVALID_PARAM_VALUE).args("bo"));
                return stringResultBean;
            }
            ArrayList<String> temp = new ArrayList<>();
            for (int i = 0; i < len; i++) {
                Object o = Array.get(bo, i);
                if (null != o) {
                    temp.add(toString(o));
                }
            }
            Optional<String> reduce = temp.stream().sorted().reduce(SignUtil::join);
            String data = reduce.get() + salt;
            ResultBean<String> success = ResultBean.success(DigestUtils.md5Hex(data), data);
            logger.debug("~~~~~~ 2 sign {}{} {}", reduce.get(), salt, success.getBean());
            return success;
        }
        //集合
        if (Collection.class.isAssignableFrom(clazz)) {
            if (((Collection<?>) bo).isEmpty()) {
                ResultBean<String> stringResultBean = ResultBean.resultCode(CommonErrCode.INVALID_PARAM_VALUE);
                stringResultBean.e(new BaseException(CommonErrCode.INVALID_PARAM_VALUE).args("bo"));
                return stringResultBean;
            }
            Optional<String> reduce = ((Collection<?>) bo).stream().filter(Objects::nonNull).map(SignUtil::toString).sorted().reduce(SignUtil::join);
            String data = reduce.get() + salt;
            ResultBean<String> success = ResultBean.success(DigestUtils.md5Hex(data), data);
            logger.debug("~~~~~~ 3 sign {}{} {}", reduce.get(), salt, success.getBean());
            return success;
        }
        TreeMap<String, String> map = new TreeMap<>();
        //处理map
        if (Map.class.isAssignableFrom(clazz)) {
            ((Map<?, ?>) bo).forEach((k, v) -> {
                if (null != k && null != v) {
                    map.put(String.valueOf(k), toString(v));
                }
            });
        } else {
            //处理bo
            BeanReflectUtil.getRWMethod(bo).forEach((k, v) -> {
                Object o = v.get(bo);
                if (null == o) {
                    return;
                }
                map.put(k, toString(o));
            });
        }
        if (map.isEmpty()) {
            logger.error("sign params map is empty bo: {} signParams: {}", bo, signParams);
            ResultBean<String> stringResultBean = ResultBean.resultCode(CommonErrCode.INVALID_PARAM_VALUE);
            stringResultBean.e(new BaseException(CommonErrCode.INVALID_PARAM_VALUE).args("bo"));
            return stringResultBean;
        }
        if (null != signParams && !signParams.isEmpty()) {
            TreeMap<String, String> temp = new TreeMap<>();
            map.forEach((k, v) -> {
                if (signParams.contains(k)) {
                    temp.put(k, v);
                }
            });
            if (temp.isEmpty()) {
                logger.error("sign params temp is empty bo: {} signParams: {}", bo, signParams);
                ResultBean<String> stringResultBean = ResultBean.resultCode(CommonErrCode.INVALID_PARAM_VALUE);
                stringResultBean.e(new BaseException(CommonErrCode.INVALID_PARAM_VALUE).args("bo"));
                return stringResultBean;
            }
            String concat = temp.values().stream().map(String::valueOf).reduce(SignUtil::join).get().concat(salt);
            ResultBean<String> success = ResultBean.success(DigestUtils.md5Hex(concat), concat);
            logger.debug("~~~~~~ 4 sign {} {}", concat, success.getBean());
            return success;
        }
        String concat = map.values().stream().map(String::valueOf).reduce(SignUtil::join).get().concat(salt);
        ResultBean<String> success = ResultBean.success(DigestUtils.md5Hex(concat), concat);
        logger.debug("~~~~~~ 5 sign {} {}", concat, success.getBean());
        return success;
    }


    /**
     * 方法描述：验证sign
     *
     * @param ao
     * @return void
     * @author 赵希坤
     * @date 日期：2023/4/1 时间：15:59
     */
    public static void checkSign(BaseSignAO ao) throws ForbiddenException {
        //校验时间戳
        if(ao.getTimestamp() == null){
            //设置1秒
            ao.setTimestamp(1000L);
        }
        if (System.currentTimeMillis() - ao.getTimestamp() > ao.getTimestamp()) {
            throw new ForbiddenException("timestamp_is_expiration");
        }
        //校验sign
        String salt = paramSecureProperties.getSignKeyMap().get(ao.getServiceId());
        String signValue = ao.getSign();
        ao.setSign(null);
        ResultBean<String> signBean = SignUtil.sign(ao, salt);
        if (!signBean.resultCode.equals("200")) {
            throw new ForbiddenException("not authorized");
        }
        if (!signValue.equals(signBean.getBean())) {
            throw new ForbiddenException("not authorized");
        }
    }


    private static String toString(Object obj) {
        return ToStringUtil.toJsonString(obj).orElse(null);
    }

    private static boolean isPrimitive(Class<?> clazz) {
        return ToStringUtil.isPrimitive(clazz);
    }

    public static void main(String[] args) {
        String salt = UUID.randomUUID().toString();
        System.out.println(sign("aaa", salt));
        System.out.println(sign(1, salt));
        System.out.println(sign(new Object(), salt));
        System.out.println(sign(new byte[]{'1', '3', '2'}, salt));
        System.out.println(sign(new byte[]{'2', '3', '1'}, salt));
        System.out.println(sign(Arrays.asList(null, "1", "2"), salt));
        System.out.println(sign(Arrays.asList("2", "1", null), salt));
    }

}
